core: Validate that xattr names aren't empty
authorColin Walters <walters@verbum.org>
Sat, 1 Jun 2024 15:29:13 +0000 (11:29 -0400)
committerColin Walters <walters@verbum.org>
Sat, 1 Jun 2024 18:46:23 +0000 (14:46 -0400)
In the ostree-ext codebase the test fixture was generating xattrs
without the trailing NUL byte.  This caused confusing errors
later.  Change the dirmeta validator to catch this.

The way GVariant represents bytestrings, the trailing NUL is there
on wire/disk so it can be there in memory too, but `g_variant_get_bytestring()`
will just return an empty `""` string if actually the value
has a missing NUL.

Signed-off-by: Colin Walters <walters@verbum.org>
src/libostree/ostree-core-private.h
src/libostree/ostree-core.c
src/libostree/ostree-repo-checkout.c
src/libostree/ostree-repo-commit.c
tests/test-basic-c.c

index 3c4b391e2023cb73f5449e6f125802238a1702d0..283944b4a9a3b8ddd50e147e0f0fa71ae3ae3221 100644 (file)
@@ -90,6 +90,7 @@ void _ostree_gfileinfo_to_stbuf (GFileInfo *file_info, struct stat *out_stbuf);
 gboolean _ostree_gfileinfo_equal (GFileInfo *a, GFileInfo *b);
 gboolean _ostree_stbuf_equal (struct stat *stbuf_a, struct stat *stbuf_b);
 GFileInfo *_ostree_mode_uidgid_to_gfileinfo (mode_t mode, uid_t uid, gid_t gid);
+gboolean _ostree_validate_structureof_xattrs (GVariant *xattrs, GError **error);
 
 static inline void
 _ostree_checksum_inplace_from_bytes_v (GVariant *csum_v, char *buf)
index b6d213478c6b494e661b87bbcbc6b982a8906dec..fb11f85bc67f6e5adb6501d503b50172844f93d1 100644 (file)
@@ -2277,6 +2277,27 @@ ostree_validate_structureof_file_mode (guint32 mode, GError **error)
   return TRUE;
 }
 
+/* Currently ostree imposes no restrictions on xattrs on its own;
+ * they can e.g. be arbitrariliy sized or in number.
+ * However, we do validate the key is non-empty, as that is known
+ * to always fail.
+ */
+gboolean
+_ostree_validate_structureof_xattrs (GVariant *xattrs, GError **error)
+{
+  const guint n = g_variant_n_children (xattrs);
+  for (guint i = 0; i < n; i++)
+    {
+      const guint8 *name;
+      g_autoptr (GVariant) value = NULL;
+      g_variant_get_child (xattrs, i, "(^&ay@ay)", &name, &value);
+      if (!*name)
+        return glnx_throw (error, "Invalid xattr name (empty or missing NUL) index=%d", i);
+      i++;
+    }
+  return TRUE;
+}
+
 /**
  * ostree_validate_structureof_dirmeta:
  * @dirmeta: A dirmeta object, %OSTREE_OBJECT_TYPE_DIR_META
@@ -2303,6 +2324,10 @@ ostree_validate_structureof_dirmeta (GVariant *dirmeta, GError **error)
   if (!validate_stat_mode_perms (mode, error))
     return FALSE;
 
+  g_autoptr (GVariant) xattrs = g_variant_get_child_value (dirmeta, 3);
+  if (!_ostree_validate_structureof_xattrs (xattrs, error))
+    return FALSE;
+
   return TRUE;
 }
 
index 575b2e6ae720e524f673fd089c097b0fda2c842a..39ecc1d59b0194398d7f8b82d6ac655bb46bc6c0 100644 (file)
@@ -1123,7 +1123,7 @@ checkout_tree_at_recurse (OstreeRepo *self, OstreeRepoCheckoutAtOptions *options
   if (!did_exist && xattrs)
     {
       if (!glnx_fd_set_all_xattrs (destination_dfd, xattrs, cancellable, error))
-        return FALSE;
+        return glnx_prefix_error (error, "Processing dirmeta %s", dirmeta_checksum);
     }
 
   /* Process files in this subdir */
index 5a22ad73118adb5e8b29e9adc5eab24f61ee4ddd..7a898757cb8f8b8d40971f4cf04a15c240ee862b 100644 (file)
@@ -139,6 +139,8 @@ gboolean
 _ostree_write_bareuser_metadata (int fd, guint32 uid, guint32 gid, guint32 mode, GVariant *xattrs,
                                  GError **error)
 {
+  if (xattrs != NULL && !_ostree_validate_structureof_xattrs (xattrs, error))
+    return FALSE;
   g_autoptr (GVariant) filemeta = create_file_metadata (uid, gid, mode, xattrs);
 
   if (TEMP_FAILURE_RETRY (fsetxattr (fd, "user.ostreemeta", (char *)g_variant_get_data (filemeta),
index a01cef6f064539afbee95f07050c49f7c0a004c8..7124b5ae9387b68931172cd9ff5e3ea226cc59a5 100644 (file)
@@ -513,6 +513,23 @@ test_read_xattrs (void)
   }
 }
 
+static void
+test_dirmeta_xattrs (void)
+{
+  g_autoptr (GError) local_error = NULL;
+  GError **error = &local_error;
+  const guint32 uidgid = GUINT32_TO_BE (42);
+  const guint32 mode = GUINT32_TO_BE (S_IFDIR | S_IRWXU);
+  g_autoptr (GVariantBuilder) xattr_builder = g_variant_builder_new (G_VARIANT_TYPE ("a(ayay)"));
+  const char *data = "data";
+  g_variant_builder_add (xattr_builder, "(@ay@ay)", g_variant_new_bytestring (""),
+                         g_variant_new_bytestring (data));
+  g_autoptr (GVariant) dirmeta = g_variant_new ("(uuu@a(ayay))", uidgid, uidgid, mode,
+                                                g_variant_builder_end (xattr_builder));
+  g_assert (!ostree_validate_structureof_dirmeta (dirmeta, error));
+  g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_FAILED);
+}
+
 int
 main (int argc, char **argv)
 {
@@ -533,6 +550,7 @@ main (int argc, char **argv)
   g_test_add_func ("/remotename", test_validate_remotename);
   g_test_add_func ("/big-metadata", test_big_metadata);
   g_test_add_func ("/read-xattrs", test_read_xattrs);
+  g_test_add_func ("/dirmeta-xattrs", test_dirmeta_xattrs);
 
   return g_test_run ();
 out: